﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel;
using Noris.Tools.QCode.QrEncoder;
using System.Drawing;

namespace Noris.Tools.QCode.UI
{
    /// <summary>
    /// Visual control (System.Windows.Forms.Control) which show QR code in Forms
    /// </summary>
    public class QControl : Control
    {
        #region Konstrukce a privátní proměnné
        public QControl()
        {
            this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            this._Init();
            this.Paint += new PaintEventHandler(_Paint);
            this.SizeChanged += new EventHandler(_SizeChanged);
        }
        private void _Init()
        {
            this._ErrorCorrectionLevel = QrEncoder.ErrorCorrectionLevel.High;
            this._ColorLight = Color.White;
            this._ColorDark = Color.Black;
            this._Time = new System.Diagnostics.Stopwatch();
            this._TimeFreq = (decimal)System.Diagnostics.Stopwatch.Frequency;
        }
        private void _SizeChanged(object sender, EventArgs e)
        {
            this.Refresh();
        }
        private void _Paint(object sender, PaintEventArgs e)
        {
            e.Graphics.Clear(this.BackColor);
            this._PrepareMatrix(this.Text, false);

            bool useOld = (this._UseOldAlgorithm && this._MatrixOld != null);
            bool useNew = (this._UseNewAlgorithm && this._MatrixNew != null);

            this._Time.Restart();

            if (useOld && useNew)
            {
                Color foreColor1 = Color.FromArgb(192, 192, 192);
                Color foreColor2 = Color.FromArgb(64, 255, 64, 64);
                this._MatrixOld.PaintCodeTo(e.Graphics, this.ClientRectangle, this._ColorLight, foreColor1); // this._ColorDark
                this._MatrixNew.PaintCodeTo(e.Graphics, this.ClientRectangle, Color.Empty, foreColor2);      // this._ColorLight, this._ColorDark
            }
            else if (useOld)
            {
                this._MatrixOld.PaintCodeTo(e.Graphics, this.ClientRectangle, this._ColorLight, this._ColorDark);
            }
            else if (useNew)
            {
                this._MatrixNew.PaintCodeTo(e.Graphics, this.ClientRectangle, this._ColorLight, this._ColorDark);
            }
            this._LastQrCodePaintTime = this._GetCurrentTime();
            
            this.OnTimeChanged();
        }
        /// <summary>
        /// Raise TimeChanged event.
        /// </summary>
        protected virtual void OnTimeChanged()
        {
            if (this.TimeChanged != null)
                this.TimeChanged(this, EventArgs.Empty);
        }
        private Rectangle _GetQrArea(out int moduleWidth)
        {
            moduleWidth = 0;
            Size clientSize = this.ClientSize;
            int minDim = (clientSize.Width < clientSize.Height ? clientSize.Width : clientSize.Height) - 4;
            if (minDim <= 0) return Rectangle.Empty;
            int matrixSize = this._MatrixOld.Width;
            moduleWidth = minDim / matrixSize;
            if (moduleWidth <= 0)
                moduleWidth = 1;

            int qrDim = moduleWidth * matrixSize;
            int x = (clientSize.Width - qrDim) / 2;
            int y = (clientSize.Height - qrDim) / 2;
            return new Rectangle(x, y, qrDim, qrDim);
        }
        private System.Diagnostics.Stopwatch _Time;
        private decimal _TimeFreq;
        #endregion
        #region Prepare matrix
        private void _PrepareMatrix()
        {
            this._PrepareMatrix(this.Text, true);
        }
        private void _PrepareMatrix(string text, bool force)
        {
            if (text == null) text = "";

            if (((!this._UseOldAlgorithm) || 
                  (this._UseOldAlgorithm && this._LastTextOld != null && text == this._LastTextOld && this._MatrixOld != null && !force))
                && 
                ((!this._UseNewAlgorithm) || 
                  (this._UseNewAlgorithm && this._LastTextNew != null && text == this._LastTextNew && this._MatrixNew != null && !force)))
                return;

            this._Time.Restart();
            if (this._UseOldAlgorithm)
            {
                QrEncoder.QrCode qrCode = QrEncoder.QrEncoder.Encode(text, this._ErrorCorrectionLevel, this._UseMatrixPattern);
                this._MatrixOld = qrCode.GetFramedMatrix(4);
                this._LastTextOld = text;
            }
            if (this._UseNewAlgorithm)
            {
                this._MatrixNew = Noris.Tools.QCode.QEncoder.QEncoder.Encode(text, this._ErrorCorrectionLevel, this._UseMatrixPattern);
                this._LastTextNew = text;
            }
            this._LastQrCodeEncodeTime = this._GetCurrentTime();
        }
        /// <summary>
        /// Zastaví timer a vrátí dosud uplynulý čas v sekundách
        /// </summary>
        /// <returns></returns>
        private decimal _GetCurrentTime()
        {
            this._Time.Stop();
            long ticks = this._Time.ElapsedTicks;
            return (decimal)ticks / this._TimeFreq;
        }
        private string _LastTextOld;
        private string _LastTextNew;
        private Noris.Tools.QCode.QrEncoder.BitMatrix _MatrixOld;
        private Noris.Tools.QCode.Common.QMatrix _MatrixNew;
        #endregion
        #region Public property
        /// <summary>
        /// Content of QR code
        /// </summary>
        [Description("Content of QR code")]
        [Category("QRCode")]
        public override string Text
        {
            get { return base.Text; }
            set
            {
                this._PrepareMatrix(value, false);
                base.Text = value;
                this.Refresh();
            }
        }
        /// <summary>
        /// Correction level applied to QR code
        /// </summary>
        [Description("Correction level applied to QR code")]
        [Category("QRCode")]
        public ErrorCorrectionLevel ErrorCorrectionLevel
        {
            get { return this._ErrorCorrectionLevel; }
            set
            {
                this._ErrorCorrectionLevel = value;
                this._PrepareMatrix();
                this.Refresh();
            }
        }
        /// <summary>
        /// Color for White points
        /// </summary>
        [Description("Color for White points")]
        [Category("QRCode")]
        public Color ColorLight
        {
            get { return this._ColorLight; }
            set
            {
                this._ColorLight = value;
                this.Refresh();
            }
        }
        /// <summary>
        /// Color for Black points
        /// </summary>
        [Description("Color for Black points")]
        [Category("QRCode")]
        public Color ColorDark
        {
            get { return this._ColorDark; }
            set
            {
                this._ColorDark = value;
                this.Refresh();
            }
        }
        /// <summary>
        /// Use Old algorithm
        /// </summary>
        [Description("Use Old algorithm")]
        [Category("QRCode")]
        public bool UseOldAlgorithm
        {
            get { return this._UseOldAlgorithm; }
            set
            {
                this._UseOldAlgorithm = value;
                this._PrepareMatrix();
                this.Refresh();
            }
        }
        /// <summary>
        /// Use New algorithm
        /// </summary>
        [Description("Use New algorithm")]
        [Category("QRCode")]
        public bool UseNewAlgorithm
        {
            get { return this._UseNewAlgorithm; }
            set
            {
                this._UseNewAlgorithm = value;
                this._PrepareMatrix();
                this.Refresh();
            }
        }
        /// <summary>
        /// Use this specified MatrixPattern / null = use all patterns
        /// </summary>
        [Description("Use this specified MatrixPattern / null = use all patterns")]
        [Category("QRCode")]
        public Int32? UseMatrixPattern
        {
            get { return this._UseMatrixPattern; }
            set
            {
                this._UseMatrixPattern = value;
                this._PrepareMatrix();
                this.Refresh();
            }
        }
        /// <summary>
        /// Last QR code encode time (in seconds)
        /// </summary>
        [Description("Last QR code encode time (in seconds)")]
        [Category("QRCode")]
        public decimal LastQrCodeEncodeTime
        {
            get { return this._LastQrCodeEncodeTime; }
        }
        /// <summary>
        /// Last QR code paint time (in seconds)
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [Browsable(false)]
        [Description("Last QR code paint time (in seconds)")]
        [Category("QRCode")]
        public decimal LastQrCodePaintTime
        {
            get { return this._LastQrCodePaintTime; }
        }
        /// <summary>
        /// Occurs whenever values LastQrCodeEncodeTime or LastQrCodePaintTime changed.
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [Browsable(false)]
        [Description("When LastQrTime value changed")]
        [Category("QRCode")]
        public event EventHandler TimeChanged;
        /// <summary>
        /// Matrice bodů QR kódu stará, zahodit
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [Browsable(false)]
        public QCode.QrEncoder.BitMatrix MatrixOld { get { return this._MatrixOld; } }
        /// <summary>
        /// Matrice bodů QR kódu
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [Browsable(false)]
        public Noris.Tools.QCode.Common.QMatrix Matrix { get { return this._MatrixNew; } }
        private ErrorCorrectionLevel _ErrorCorrectionLevel;
        private Color _ColorLight;
        private Color _ColorDark;
        private bool _UseOldAlgorithm;
        private bool _UseNewAlgorithm;
        private Int32? _UseMatrixPattern;
        private decimal _LastQrCodeEncodeTime;
        private decimal _LastQrCodePaintTime;
        #endregion
    }
}
